//func.c

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "logger.h"
#include "sockopt.h"
#include "shared_func.h"
#include "ini_file_reader.h"
#include "global.h"
#include "fdht_types.h"
#include "fdht_func.h"
#include "func.h"

int group_cmp_by_ip_and_port(const void *p1, const void *p2)
{
	int res;

	res = strcmp(((FDHTGroupServer *)p1)->ip_addr, \
			((FDHTGroupServer *)p2)->ip_addr);
	if (res != 0)
	{
		return res;
	}

	return ((FDHTGroupServer *)p1)->port - \
			((FDHTGroupServer *)p2)->port;
}

static int fnio_load_server_groups(IniItemInfo *items, const int nItemCount)
{
	char *pAddr;

	memset(&g_group_array, 0, sizeof(g_group_array));

	g_group_array.group_count = 1;
	g_group_array.server_count = 1;
	g_group_array.servers = (FDHTServerInfo *)malloc( \
			g_group_array.server_count * sizeof(FDHTServerInfo));
	if (g_group_array.servers == NULL)
	{
		logError("file: "__FILE__", line: %d, " \
			"malloc %d bytes fail, " \
			"errno: %d, error info: %s", \
			__LINE__, g_group_array.server_count * \
			sizeof(FDHTServerInfo), errno, strerror(errno));
		return errno != 0 ? errno : ENOMEM;
	}

	g_group_array.groups = (ServerArray *)malloc( \
			g_group_array.group_count * sizeof(ServerArray));
	if (g_group_array.groups == NULL)
	{
		logError("file: "__FILE__", line: %d, " \
			"malloc %d bytes fail, " \
			"errno: %d, error info: %s", \
			__LINE__, g_group_array.group_count * \
			sizeof(ServerArray), errno, strerror(errno));
		return errno != 0 ? errno : ENOMEM;
	}

	g_group_array.groups[0].count = 1;
	g_group_array.groups[0].servers = (FDHTServerInfo **)malloc( \
			g_group_array.groups[0].count * \
			sizeof(FDHTServerInfo *));
	if (g_group_array.groups[0].servers == NULL)
	{
		logError("file: "__FILE__", line: %d, " \
			"malloc %d bytes fail, " \
			"errno: %d, error info: %s", \
			__LINE__, g_group_array.groups[0].count * \
			sizeof(FDHTServerInfo *), errno, strerror(errno));
		return errno != 0 ? errno : ENOMEM;
	}
	*(g_group_array.groups[0].servers) = g_group_array.servers;

	pAddr = iniGetStrValue("dest_server_addr", items, nItemCount);
	if (pAddr == NULL || *pAddr == '\0')
	{
		strcpy(g_group_array.servers->ip_addr, "127.0.0.1");
	}
	else
	{
		snprintf(g_group_array.servers->ip_addr, \
			sizeof(g_group_array.servers->ip_addr), "%s", pAddr);
	}

	g_group_array.servers->port = iniGetIntValue("dest_server_port", \
			items, nItemCount, FNIO_PROXY_CLIENT_DEFAULT_PORT);
	if (g_group_array.servers->port <= 0 || \
		g_group_array.servers->port > 65535)
	{
		logError("file: "__FILE__", line: %d, " \
			"invalid server port: %d", \
			__LINE__, g_group_array.servers->port);
		return EINVAL;
	}
	g_group_array.servers->sock = -1;

	return 0;
}

int fnio_func_init(const char *filename, char *bind_addr, const int addr_size)
{
	char *pBasePath;
	char *pServerMode;
	char *pBindAddr;
	char *pRunByGroup;
	char *pRunByUser;
	char *pMaxPkgSize;
	char *pMinBuffSize;
	char *pMaxBuffSize;
	char *pLogFilePrefix;
	IniItemInfo *items;
	int nItemCount;
	int result;
	int64_t max_pkg_size;
	int64_t min_buff_size;
	int64_t max_buff_size;

	if ((result=iniLoadItems(filename, &items, &nItemCount)) != 0)
	{
		logError("file: "__FILE__", line: %d, " \
			"load conf file \"%s\" fail, ret code: %d", \
			__LINE__, filename, result);
		return result;
	}

	//iniPrintItems(items, nItemCount);

	while (1)
	{
		if (iniGetBoolValue("disabled", items, nItemCount, false))
		{
			logError("file: "__FILE__", line: %d, " \
				"conf file \"%s\" disabled=true, exit", \
				__LINE__, filename);
			result = ECANCELED;
			break;
		}

		pBasePath = iniGetStrValue("base_path", items, nItemCount);
		if (pBasePath == NULL)
		{
			logError("file: "__FILE__", line: %d, " \
				"conf file \"%s\" must have item " \
				"\"base_path\"!", \
				__LINE__, filename);
			result = ENOENT;
			break;
		}

		snprintf(g_base_path, sizeof(g_base_path), "%s", pBasePath);
		chopPath(g_base_path);
		if (!fileExists(g_base_path))
		{
			logError("file: "__FILE__", line: %d, " \
				"\"%s\" can't be accessed, error info: %s", \
				__LINE__, strerror(errno), g_base_path);
			result = errno != 0 ? errno : ENOENT;
			break;
		}
		if (!isDir(g_base_path))
		{
			logError("file: "__FILE__", line: %d, " \
				"\"%s\" is not a directory!", \
				__LINE__, g_base_path);
			result = ENOTDIR;
			break;
		}

		pServerMode = iniGetStrValue("server_mode", items, nItemCount);
		if (pServerMode == NULL)
		{
			logError("file: "__FILE__", line: %d, " \
				"conf file \"%s\" must have item " \
				"\"server_mode\"!", \
				__LINE__, filename);
			result = ENOENT;
			break;
		}

		if (strcasecmp(pServerMode, "server") == 0)
		{
			g_server_mode = FNIO_SERVER_MODE_SERVER;
			pLogFilePrefix = "fnio_server";
		}
		else if (strcasecmp(pServerMode, "client") == 0)
		{
			g_server_mode = FNIO_SERVER_MODE_CLIENT;
			pLogFilePrefix = "fnio_client";
		}
		else
		{
			logError("file: "__FILE__", line: %d, " \
				"server_mode \"%s\" is invalid, " \
				"must be \"server\" or \"client\"!", \
				__LINE__, pServerMode);

			result = EINVAL;
			break;
		}

		load_log_level(items, nItemCount);
		if ((result=log_init(g_base_path, pLogFilePrefix)) != 0)
		{
			break;
		}

		g_network_timeout = iniGetIntValue("network_timeout", \
				items, nItemCount, DEFAULT_NETWORK_TIMEOUT);
		if (g_network_timeout <= 0)
		{
			g_network_timeout = DEFAULT_NETWORK_TIMEOUT;
		}

		g_network_tv.tv_sec = g_network_timeout;

		g_proxy_client_port = iniGetIntValue("proxy_client_port", \
			items, nItemCount, FNIO_PROXY_CLIENT_DEFAULT_PORT);
		if (g_proxy_client_port <= 0)
		{
			g_proxy_client_port = FNIO_PROXY_CLIENT_DEFAULT_PORT;
		}

		g_proxy_server_port = iniGetIntValue("proxy_server_port", \
			items, nItemCount, FNIO_PROXY_SERVER_DEFAULT_PORT);
		if (g_proxy_server_port <= 0)
		{
			g_proxy_server_port = FNIO_PROXY_SERVER_DEFAULT_PORT;
		}

		pBindAddr = iniGetStrValue("bind_addr", items, nItemCount);
		if (pBindAddr == NULL)
		{
			bind_addr[0] = '\0';
		}
		else
		{
			snprintf(bind_addr, addr_size, "%s", pBindAddr);
		}

		g_max_connections = iniGetIntValue("max_connections", \
				items, nItemCount, DEFAULT_MAX_CONNECTONS);
		if (g_max_connections <= 0)
		{
			g_max_connections = DEFAULT_MAX_CONNECTONS;
		}
		if ((result=set_rlimit(RLIMIT_NOFILE, g_max_connections)) != 0)
		{
			break;
		}

		if (g_server_mode == FNIO_SERVER_MODE_SERVER)
		{
			g_server_work_threads = iniGetIntValue( \
				"server_work_threads", items, nItemCount, \
				FNIO_DEFAULT_SERVER_WORK_THREADS);
			if (g_server_work_threads <= 0)
			{
				g_server_work_threads = \
					FNIO_DEFAULT_SERVER_WORK_THREADS;
			}
		}
		else  //client
		{
			g_server_work_threads = 1;
		}

		pMaxPkgSize = iniGetStrValue("max_pkg_size", \
				items, nItemCount);
		if (pMaxPkgSize == NULL)
		{
			g_max_pkg_size = FNIO_MAX_PKG_SIZE;
		}
		else 
		{
			if ((result=parse_bytes(pMaxPkgSize, 1, \
					&max_pkg_size)) != 0)
			{
				return result;
			}
			g_max_pkg_size = (int)max_pkg_size;
		}

		pMinBuffSize = iniGetStrValue("min_buff_size", \
				items, nItemCount);
		if (pMinBuffSize == NULL)
		{
			g_min_buff_size = FNIO_MIN_BUFF_SIZE;
		}
		else
		{
			if ((result=parse_bytes(pMinBuffSize, 1, \
					&min_buff_size)) != 0)
			{
				return result;
			}
			g_min_buff_size = (int)min_buff_size;
		}

		pMaxBuffSize = iniGetStrValue("max_buff_size", \
				items, nItemCount);
		if (pMaxBuffSize == NULL)
		{
			g_max_buff_size = FNIO_MAX_BUFF_SIZE;
		}
		else
		{
			if ((result=parse_bytes(pMaxBuffSize, 1, \
					&max_buff_size)) != 0)
			{
				return result;
			}
			g_max_buff_size = (int)max_buff_size;
		}

		pRunByGroup = iniGetStrValue("run_by_group", \
						items, nItemCount);
		pRunByUser = iniGetStrValue("run_by_user", \
						items, nItemCount);
		if ((result=set_run_by(pRunByGroup, pRunByUser)) != 0)
		{
			break;
		}

		if ((result=load_allow_hosts(items, nItemCount, \
                	 &g_allow_ip_addrs, &g_allow_ip_count)) != 0)
		{
			break;
		}

		if (g_server_mode == FNIO_SERVER_MODE_CLIENT)
		{
			if ((result=fdht_load_groups_ex(items, nItemCount, \
					&g_group_array, false)) != 0)
			{
				break;
			}
		}
		else
		{
			if ((result=fnio_load_server_groups(items, \
				nItemCount)) != 0)
			{
				break;
			}
		}

		g_sync_log_buff_interval = iniGetIntValue( \
				"sync_log_buff_interval", items, nItemCount, \
				SYNC_LOG_BUFF_DEF_INTERVAL);
		if (g_sync_log_buff_interval <= 0)
		{
			g_sync_log_buff_interval = SYNC_LOG_BUFF_DEF_INTERVAL;
		}

		if (g_server_mode == FNIO_SERVER_MODE_CLIENT)
		{
			logInfo("FastNIO v%d.%02d, " \
				"base_path=%s, server_mode=%s, " \
				"FastDHT group count=%d, server count=%d, " \
				"network_timeout=%d, " \
				"proxy_client_port=%d, " \
				"proxy_server_port=%d, " \
				"bind_addr=%s, " \
				"max_connections=%d, "    \
				"max_pkg_size=%d KB, " \
				"min_buff_size=%d KB, " \
				"max_buff_size=%d KB, " \
				"allow_ip_count=%d, " \
				"sync_log_buff_interval=%ds", \
				g_version.major, g_version.minor, \
				g_base_path, pServerMode, \
				g_group_array.group_count, \
				g_group_array.server_count, g_network_timeout, \
				g_proxy_client_port, g_proxy_server_port, \
				bind_addr, g_max_connections, \
				g_max_pkg_size / 1024, \
				g_min_buff_size / 1024, g_max_buff_size / 1024,\
				g_allow_ip_count, \
				g_sync_log_buff_interval);
		}
		else
		{
			logInfo("FastNIO v%d.%02d, " \
				"base_path=%s, server_mode=%s, " \
				"network_timeout=%d, " \
				"proxy_client_port=%d, " \
				"proxy_server_port=%d, " \
				"dest_server_addr=%s, " \
				"dest_server_port=%d, " \
				"bind_addr=%s, " \
				"max_connections=%d, "    \
				"server_work_threads=%d, "   \
				"max_pkg_size=%d KB, " \
				"min_buff_size=%d KB, " \
				"max_buff_size=%d KB, " \
				"allow_ip_count=%d, " \
				"sync_log_buff_interval=%ds", \
				g_version.major, g_version.minor, \
				g_base_path, pServerMode, \
				g_network_timeout, \
				g_proxy_client_port, g_proxy_server_port, \
				g_group_array.servers->ip_addr, \
				g_group_array.servers->port, \
				bind_addr, g_max_connections, \
				g_server_work_threads, g_max_pkg_size / 1024, \
				g_min_buff_size / 1024, g_max_buff_size / 1024,\
				g_allow_ip_count, \
				g_sync_log_buff_interval);
		}

		break;
	}

	iniFreeItems(items);

	return result;
}

void fnio_func_destroy()
{
	fdht_free_group_array(&g_group_array);
}

